package jm.easyconfig;

import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class PollingUpdater extends ConfigUpdater {
	CfgContextEnvironmentAware ctxAware;
	MySqlConfigSource configSource;

	long localLastTime;
	long scheduledTick;
	ScheduledExecutorService backgroundScheduler;
	RedisCacheHelper cacheHelper;

	float pollingPeriod = 0;

	@EasyValue(value = "${easyconfig.polling.period:5}", name = "Time(s) to check data source")
	public void setPollingPeriod(float pollingPeriod) {

		pollingPeriod = pollingPeriod < 0.01f ? 0.01f : pollingPeriod;

		if (Math.abs(this.pollingPeriod - pollingPeriod) < 0.001)
			return;

		this.pollingPeriod = pollingPeriod;

		this.startup();
	}

	public float getPollingPeriod() {
		return pollingPeriod;
	}

	@EasyValue(value = "${easyconfig.polling.forced:120}", name = "Count of polling period to refresh forcedly")
	long refreshForced = 120L;

	@Override
	public void init(MySqlConfigSource configSource, CfgContextEnvironmentAware ctxAware) {
		this.configSource = configSource;
		this.ctxAware = ctxAware;

		cacheHelper = new RedisCacheHelper();

		ctxAware.profilesHelper.replaceEnvProperties(refreshToLatest());
	}

	void startup() {
		this.close();

		backgroundScheduler = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "easyconfig_scheduler"));
		backgroundScheduler.scheduleWithFixedDelay(this::scheduleToCheck, 1000, (long) pollingPeriod * 1000,
				TimeUnit.MILLISECONDS);

		CfgHelper.logger.info("Start a scheduler to check config with polling period of {} ms", pollingPeriod);
	}

	@Override
	public void publish(List<ConfigItemCore> updatedResult, List<String> nodeList) {
		boolean includeAll = this.localLastTime == 0L;
		List<ConfigItemCore> list = refreshToLatest();

		if (list.size() > 0) {
			ConfigRefreshEvent event = new ConfigRefreshEvent(this, list, includeAll);
			CfgContextEnvironmentAware.applicationContext.publishEvent(event);
		}
	}

	List<ConfigItemCore> refreshToLatest() {
		Holder<Long> timeHolder = new Holder<Long>(this.localLastTime);
		List<ConfigItemCore> list = configSource.queryAfterTime(timeHolder);

		if (list.size() > 0) {
			this.localLastTime = timeHolder.value;

			if (cacheHelper != null/* for test scene */ && cacheHelper.isAvailable()) {
				cacheHelper.setSourceTimestamp(timeHolder.value);
			}
		}

		return list;
	}

	void scheduleToCheck() {
		try {
			scheduledTick++;

			if (scheduledTick % refreshForced == 0) {
				this.localLastTime = 0;
				CfgHelper.logger.debug("The config is forced to refresh from datasource.");
			}

			if (!cacheHelper.isAvailable() || !cacheHelper.sameWith(this.localLastTime)) {
				this.publish(null, null);
			}

		} catch (Throwable ex) {
			CfgHelper.logger.error("error occured while refreshing config data source. message:{}", ex.getMessage());
		}
	}

	@Override
	public void close() {
		if (backgroundScheduler != null) {
			backgroundScheduler.shutdown();
			backgroundScheduler = null;
		}
	}

}
